Hi 大家,經過前幾天的學習,今天我們要整合所有學到的概念,建立第一個完整的 Rust 專案:終端機任務管理器。這個專案會運用到變數、函式、控制流程、迭代器等所有概念,體驗完整的 Rust 開發流程。
# 建立新專案
cargo new task_manager
cd task_manager
# 建立檔案結構
mkdir src
touch src/task.rs src/utils.rs
# 編譯專案
cargo build
# 執行專案
cargo run
task_manager/
├── Cargo.toml
├── src/
│ ├── main.rs
│ ├── task.rs
│ └── utils.rs
[package]
name = "task_manager"
version = "0.1.0"
edition = "2024"
[dependencies]
use std::fmt;
#[derive(Debug, Clone)]
pub struct Task {
pub id: usize,
pub title: String,
pub description: String,
pub completed: bool,
pub priority: Priority,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Priority {
Low,
Medium,
High,
Urgent,
}
impl fmt::Display for Priority {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let priority_str = match self {
Priority::Low => "低",
Priority::Medium => "中",
Priority::High => "高",
Priority::Urgent => "緊急",
};
write!(f, "{}", priority_str)
}
}
impl Task {
pub fn new(id: usize, title: String, description: String, priority: Priority) -> Self {
Task {
id,
title,
description,
completed: false,
priority,
tags: Vec::new(),
}
}
pub fn toggle_completion(&mut self) {
self.completed = !self.completed;
}
pub fn add_tag(&mut self, tag: String) {
if !self.tags.contains(&tag) {
self.tags.push(tag);
}
}
pub fn matches_keyword(&self, keyword: &str) -> bool {
let keyword = keyword.to_lowercase();
self.title.to_lowercase().contains(&keyword) ||
self.description.to_lowercase().contains(&keyword) ||
self.tags.iter().any(|tag| tag.to_lowercase().contains(&keyword))
}
}
impl fmt::Display for Task {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let status = if self.completed { "✓" } else { "○" };
let tags_str = if self.tags.is_empty() {
String::new()
} else {
format!(" [{}]", self.tags.join(", "))
};
write!(f, "{} [{}] {} - {} (優先度: {}){}",
status, self.id, self.title, self.description, self.priority, tags_str)
}
}
use std::io;
pub fn get_user_input(prompt: &str) -> String {
println!("{}", prompt);
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("讀取輸入失敗");
input.trim().to_string()
}
pub fn get_number_input(prompt: &str) -> Option<usize> {
let input = get_user_input(prompt);
input.parse().ok()
}
pub fn confirm_action(prompt: &str) -> bool {
loop {
let input = get_user_input(&format!("{} (y/n)", prompt));
match input.to_lowercase().as_str() {
"y" | "yes" | "是" => return true,
"n" | "no" | "否" => return false,
_ => println!("請輸入 y 或 n"),
}
}
}
pub fn clear_screen() {
print!("\x1B[2J\x1B[1;1H");
}
mod task;
mod utils;
use task::{Task, Priority};
use utils::{get_user_input, get_number_input, confirm_action, clear_screen};
use std::collections::HashMap;
struct TaskManager {
tasks: Vec<Task>,
next_id: usize,
}
impl TaskManager {
fn new() -> Self {
TaskManager {
tasks: Vec::new(),
next_id: 1,
}
}
fn add_task(&mut self) {
println!("\n=== 新增任務 ===");
let title = get_user_input("任務標題:");
if title.is_empty() {
println!("標題不能為空!");
return;
}
let description = get_user_input("任務描述:");
let priority = loop {
println!("選擇優先度:");
println!("1. 低");
println!("2. 中");
println!("3. 高");
println!("4. 緊急");
match get_number_input("請選擇 (1-4):") {
Some(1) => break Priority::Low,
Some(2) => break Priority::Medium,
Some(3) => break Priority::High,
Some(4) => break Priority::Urgent,
_ => println!("請輸入 1-4"),
}
};
let mut task = Task::new(self.next_id, title, description, priority);
// 新增標籤
let tags_input = get_user_input("標籤 (用逗號分隔,可留空):");
if !tags_input.is_empty() {
for tag in tags_input.split(',') {
let tag = tag.trim().to_string();
if !tag.is_empty() {
task.add_tag(tag);
}
}
}
self.tasks.push(task);
self.next_id += 1;
println!("✓ 任務新增成功!");
}
fn list_tasks(&self) {
println!("\n=== 任務清單 ===");
if self.tasks.is_empty() {
println!("目前沒有任務");
return;
}
// 按優先度和完成狀態分組顯示
let incomplete_tasks: Vec<&Task> = self.tasks
.iter()
.filter(|task| !task.completed)
.collect();
let completed_tasks: Vec<&Task> = self.tasks
.iter()
.filter(|task| task.completed)
.collect();
if !incomplete_tasks.is_empty() {
println!("\n📋 待辦任務:");
for task in incomplete_tasks {
println!(" {}", task);
}
}
if !completed_tasks.is_empty() {
println!("\n✅ 已完成任務:");
for task in completed_tasks {
println!(" {}", task);
}
}
self.show_statistics();
}
fn show_statistics(&self) {
let total = self.tasks.len();
let completed = self.tasks.iter().filter(|task| task.completed).count();
let completion_rate = if total > 0 {
(completed as f64 / total as f64) * 100.0
} else {
0.0
};
println!("\n📊 統計資訊:");
println!(" 總任務數: {}", total);
println!(" 已完成: {}", completed);
println!(" 完成率: {:.1}%", completion_rate);
// 優先度統計
let priority_stats = self.tasks
.iter()
.filter(|task| !task.completed)
.fold(HashMap::new(), |mut acc, task| {
*acc.entry(&task.priority).or_insert(0) += 1;
acc
});
if !priority_stats.is_empty() {
println!(" 待辦任務優先度分布:");
for (priority, count) in priority_stats {
println!(" {}: {} 個", priority, count);
}
}
}
fn toggle_task_completion(&mut self) {
if self.tasks.is_empty() {
println!("沒有任務可以操作");
return;
}
self.list_tasks();
if let Some(id) = get_number_input("\n請輸入要切換狀態的任務 ID:") {
if let Some(task) = self.tasks.iter_mut().find(|task| task.id == id) {
task.toggle_completion();
let status = if task.completed { "完成" } else { "未完成" };
println!("✓ 任務 '{}' 已標記為{}", task.title, status);
} else {
println!("找不到 ID 為 {} 的任務", id);
}
}
}
fn delete_task(&mut self) {
if self.tasks.is_empty() {
println!("沒有任務可以刪除");
return;
}
self.list_tasks();
if let Some(id) = get_number_input("\n請輸入要刪除的任務 ID:") {
if let Some(pos) = self.tasks.iter().position(|task| task.id == id) {
let task = &self.tasks[pos];
if confirm_action(&format!("確定要刪除任務 '{}'?", task.title)) {
let removed_task = self.tasks.remove(pos);
println!("✓ 已刪除任務: {}", removed_task.title);
}
} else {
println!("找不到 ID 為 {} 的任務", id);
}
}
}
fn search_tasks(&self) {
let keyword = get_user_input("請輸入搜尋關鍵字:");
if keyword.is_empty() {
println!("關鍵字不能為空");
return;
}
let matching_tasks: Vec<&Task> = self.tasks
.iter()
.filter(|task| task.matches_keyword(&keyword))
.collect();
println!("\n=== 搜尋結果: '{}' ===", keyword);
if matching_tasks.is_empty() {
println!("沒有找到相關任務");
} else {
println!("找到 {} 個相關任務:", matching_tasks.len());
for task in matching_tasks {
println!(" {}", task);
}
}
}
fn export_tasks(&self) {
if self.tasks.is_empty() {
println!("沒有任務可以匯出");
return;
}
println!("\n=== 匯出任務清單 ===");
println!("# 任務清單報告\n");
// 按優先度分組
let priority_groups = [
(Priority::Urgent, "🔥 緊急任務"),
(Priority::High, "⚡ 高優先度任務"),
(Priority::Medium, "📋 中優先度任務"),
(Priority::Low, "📝 低優先度任務"),
];
for (priority, title) in priority_groups {
let tasks_in_priority: Vec<&Task> = self.tasks
.iter()
.filter(|task| task.priority == priority && !task.completed)
.collect();
if !tasks_in_priority.is_empty() {
println!("## {}", title);
for task in tasks_in_priority {
println!("- [{}] {} - {}",
if task.completed { "x" } else { " " },
task.title,
task.description
);
if !task.tags.is_empty() {
println!(" 標籤: {}", task.tags.join(", "));
}
}
println!();
}
}
// 已完成任務
let completed_tasks: Vec<&Task> = self.tasks
.iter()
.filter(|task| task.completed)
.collect();
if !completed_tasks.is_empty() {
println!("## ✅ 已完成任務");
for task in completed_tasks {
println!("- [x] {} - {}", task.title, task.description);
}
}
self.show_statistics();
}
}
fn main() {
let mut task_manager = TaskManager::new();
// 新增一些範例任務
task_manager.tasks.push(Task::new(
1,
"學習 Rust".to_string(),
"完成 30 天 Rust 教學".to_string(),
Priority::High
));
task_manager.tasks[0].add_tag("程式設計".to_string());
task_manager.tasks[0].add_tag("學習".to_string());
task_manager.next_id = 2;
println!("🦀 歡迎使用 Rust 任務管理器!");
loop {
println!("\n{}", "=".repeat(40));
println!("📋 任務管理器主選單");
println!("{}", "=".repeat(40));
println!("1. 📝 新增任務");
println!("2. 📋 列出任務");
println!("3. ✅ 切換任務狀態");
println!("4. 🗑️ 刪除任務");
println!("5. 🔍 搜尋任務");
println!("6. 📤 匯出任務清單");
println!("7. 🧹 清理已完成任務");
println!("8. 🚪 離開程式");
match get_number_input("請選擇操作 (1-8):") {
Some(1) => task_manager.add_task(),
Some(2) => task_manager.list_tasks(),
Some(3) => task_manager.toggle_task_completion(),
Some(4) => task_manager.delete_task(),
Some(5) => task_manager.search_tasks(),
Some(6) => task_manager.export_tasks(),
Some(7) => cleanup_completed_tasks(&mut task_manager),
Some(8) => {
if confirm_action("確定要離開嗎?") {
println!("感謝使用任務管理器!👋");
break;
}
}
_ => {
println!("❌ 無效選擇,請輸入 1-8");
continue;
}
}
// 暫停讓使用者查看結果
if get_user_input("\n按 Enter 繼續...").is_empty() || true {
clear_screen();
}
}
}
fn cleanup_completed_tasks(task_manager: &mut TaskManager) {
let completed_count = task_manager.tasks
.iter()
.filter(|task| task.completed)
.count();
if completed_count == 0 {
println!("沒有已完成的任務需要清理");
return;
}
if confirm_action(&format!("確定要刪除 {} 個已完成的任務嗎?", completed_count)) {
task_manager.tasks.retain(|task| !task.completed);
println!("✓ 已清理 {} 個已完成任務", completed_count);
}
}
impl TaskManager {
fn filter_tasks_by_priority(&self, priority: Priority) {
let filtered_tasks: Vec<&Task> = self.tasks
.iter()
.filter(|task| task.priority == priority && !task.completed)
.collect();
println!("\n=== {} 優先度任務 ===", priority);
if filtered_tasks.is_empty() {
println!("沒有符合條件的任務");
} else {
for task in filtered_tasks {
println!(" {}", task);
}
}
}
fn show_tasks_by_tag(&self) {
let tag = get_user_input("請輸入標籤名稱:");
let tagged_tasks: Vec<&Task> = self.tasks
.iter()
.filter(|task| task.tags.iter().any(|t| t.contains(&tag)))
.collect();
println!("\n=== 標籤 '{}' 的任務 ===", tag);
if tagged_tasks.is_empty() {
println!("沒有找到相關標籤的任務");
} else {
for task in tagged_tasks {
println!(" {}", task);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_task_creation() {
let task = Task::new(
1,
"測試任務".to_string(),
"這是一個測試".to_string(),
Priority::Medium
);
assert_eq!(task.id, 1);
assert_eq!(task.title, "測試任務");
assert!(!task.completed);
}
#[test]
fn test_task_completion() {
let mut task = Task::new(
1,
"測試".to_string(),
"描述".to_string(),
Priority::Low
);
assert!(!task.completed);
task.toggle_completion();
assert!(task.completed);
}
#[test]
fn test_task_search() {
let task = Task::new(
1,
"學習 Rust".to_string(),
"程式設計學習".to_string(),
Priority::High
);
assert!(task.matches_keyword("rust"));
assert!(task.matches_keyword("程式"));
assert!(!task.matches_keyword("java"));
}
}
實作任務按不同條件排序的功能:
新增更詳細的統計功能:
將任務資料儲存到檔案:
// 問題 1: 借用檢查器錯誤
// 錯誤寫法
fn bad_example(tasks: &mut Vec<Task>) {
for task in tasks.iter() {
if task.completed {
tasks.remove(0); // 錯誤:在借用期間修改
}
}
}
// 正確寫法
fn good_example(tasks: &mut Vec<Task>) {
tasks.retain(|task| !task.completed);
}
// 問題 2: 字串所有權
// 錯誤寫法
fn bad_string_handling() -> String {
let s = "Hello".to_string();
s.as_str() // 錯誤:回傳借用而非擁有
}
// 正確寫法
fn good_string_handling() -> String {
let s = "Hello".to_string();
s // 回傳所有權
}
impl TaskManager {
fn get_urgent_incomplete_tasks(&self) -> Vec<&Task> {
self.tasks
.iter()
.filter(|task| !task.completed)
.filter(|task| task.priority == Priority::Urgent)
.collect()
}
fn count_tasks_by_status(&self) -> (usize, usize) {
self.tasks
.iter()
.fold((0, 0), |(completed, incomplete), task| {
if task.completed {
(completed + 1, incomplete)
} else {
(completed, incomplete + 1)
}
})
}
}
恭喜你完成了第一週的學習!
我們將開始導入所有權系統,Stay tuned!